跳到主要内容

Java Netty 各组件的作用

Channel 接口

Channel 接口是连接到网络套接字或能够进行读写、连接和绑定等 IO 操作的组件。它内置基本的 I/O 操作(bind()connect()read()write())依赖于底层网络传输所提供的原语。

Netty 的 Channel 接口所提供的 API,大大地降低了直接使用 Socket 类的复杂性。此外,Channel 也是拥有许多预定义的、专门化实现的广泛类层次结构的根,下面是一个简短的部分清单:

  • EmbeddedChannel;
  • LocalServerChannel;
  • NioDatagramChannel;
  • NioSctpChannel;
  • NioSocketChannel。

Channel 接口层次

Channel 的常用方法

1、close() 可以用来关闭 Channel

2、closeFuture() 用来处理 Channel 的关闭,其中 sync 方法作用是同步(阻塞)等待 Channel 关闭,而 addListener 方法是异步等待 Channel 关闭

3、pipeline() 方法用于添加处理器

4、write() 方法将数据写入

5、因为缓冲机制,数据被写入到 Channel 中以后,不会立即被发送只有当缓冲满了或者调用了 flush() 方法后,才会将数据通过 Channel 发送出去,但是调用 writeAndFlush() 方法将数据写入并立即发送(刷出)

EventLoop 接口

EventLoop 定义了 Netty 的核心抽象,用于处理连接的生命周期中所发生的事件。具体细节之后讲。

下图展示了 Channel、EventLoop 、Thread 以及 EventLoopGroup 之间的关系。

这些关系是:

  • 一个 EventLoopGroup 包含一个或者多个 EventLoop
  • 一个 EventLoop 在它的生命周期内只和一个 Thread 绑定
  • 所有由 EventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理
  • 一个 Channel 在它的生命周期内只注册于一个 EventLoop
  • 一个 EventLoop 可能会被分配给一个或多个 Channel。

注意,在这种设计中,一个给定 Channel 的 I/O 操作都是由相同的 Thread 执行的,实际上消除了对于同步的需要。

EventLoopGroup 和 EventLoop 关系

ChannelFuture 接口

Netty 中所有的 I/O 操作都是异步的。因为一个操作可能不会立即返回,所以我们需要一种用于在之后的某个时间点确定其结果的方法。为此,Netty提供了 ChannelFuture 接口,其 addListener() 方法注册了一个 ChannelFutureListener,以便在某个操作完成时(无论是否成功)得到通知

Netty 中的 Future 与 JDK 中的 Future 同名,但是是两个接口

JDK Future 只能同步等待任务结束(或成功、或失败)才能得到结果 netty Future 可以同步等待任务结束得到结果,也可以异步方式得到结果,但都是要等任务结束

ChannelHandler 和 ChannelPipeline

Handler 就是一个处理器,它有上图所示的两种,主要就是出站处理器,和入站处理器,而 Pipeline 则是编排这些 Handler(Pipeline 会自动取得这两种处理器)的管道,消息就是按照 Pipeline 编排的顺序在一个个 Handler 之间流动

它们的关系如图所示:

再介绍一下它们之间是怎么联系的

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf in = (ByteBuf) msg;
System.out.println("Server received:" + in.toString(Charset.defaultCharset()));
// 再次将接收到的消息发回给发送者,而不冲刷出站消息
ctx.write(in);
}

ChannelHandlerContext 用于管理它所关联的 ChannelHandler 和同一个 ChannelPipeline 中的下一个 ChannelHandler 的交互(每当有 handler 添加到 Pipeline 时,都会创建 context,创建之后 context 和 handler 的关系永远都不会变,因而可以缓存 context 的引用),如果事件从 channel 或者 channelpipeline 上触发将沿整个 pipeline 传播,但是 context 上的相同触发方式只会传递给 pipeline 上的下一个能够处理的 handler

深入学习 ChannelHandler

Netty 提供了大量适配器形式的默认 ChannelHandler,只需去拓展它们就可以用了,常用的适配器如下:

  • ChannelHandlerAdapter
  • ChannelInboundHandlerAdapter
  • ChannelOutboundHandlerAdapter
  • ChannelDuplexHandler

以下介绍一下 ChannelHandler 的几个子类型

编码器和解码器

当你通过 Netty 发送或者接收一个消息的时候,就将会发生一次数据转换。

  • 入站消息会被解码;也就是说,从字节转换为另一种格式,通常是一个 Java 对象。
  • 而如果是出站消息,则会发生相反方向的转换:它将从它的当前格式被编码为字节。

这两种方向的转换的原因很简单:网络数据总是一系列的字节。对应于特定的需要,Netty 为编码器和解码器提供了不同类型的抽象类。

通常这些基类名称为:ByteToMessageDecoder 或者 MessageToByteEncoder 这种

一般 Netty 提供的这些编码器和解码器都实现了 ChannelInboundHandler 或者 ChannelOutboundHandler 接口

抽象类 SimpleChannelInboundHandler

上面说到了有编码器和解码器这种东西,为了避免每次都要指定它们,Netty 提供了 SimpleChannelInboundHandler 这种抽象类,它是一个泛型,只需 <T> 写上要处理的 Java 消息类型,它就会自动转码

// 如下
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf>

这个接口其它内容之后再讲

引导程序 Bootstrap

Netty的引导类为应用程序的网络层配置提供了容器,这涉及将一个进程绑定到某个指定的端口,或者将一个进程连接到另一个运行在某个指定主机的指定端口上的进程。

通常来说,我们把前面的用例称作引导一个服务器,后面的用例称作引导一个客户端。虽然这个术语简单方便,但是它略微掩盖了一 个重要的事实,即 “服务器” 和 “客户端” 实际上表示了不同的网络行为;换句话说,是监听传入的连接还是建立到一个或者多个进程的连接。

因此,有两种类型的引导:一种用于客户端(简单地称为 Bootstrap),而另种(ServerBootstrap)用于服务器。无论你的应用程序使用哪种协议或者处理哪种类型的数据,唯一决定它使用哪种引导类的是它是作为一个客户端还是作为一个服务器。

这里 ServerBootstrap 需要两个 EventLoopGroup 是因为服务器需要两组不同的 Channel。

  • 第一组将只包含一个 ServerChannel,代表服务器自身的已绑定到某个本地端口的正在监听的套接字。
  • 而第二组将包含所有已创建的用来处理传入客户端连接(对于每个服务器已经接受的连接都有一个)的 Channel 。

与 ServerChannel 相关联的 EventLoopGroup 将分配一个 负责为传入连接请求创建 Channel 的 EventLoop。一旦连接被接受,第二个 EventLoopGroup 就会给它的 Channel 分配一个 EventLoop。